﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;
using Roslyn.Compilers.Common;
using Roslyn.Services;
using Roslyn.Services.FindReferences;
using System.IO;
using System.Reflection;

namespace RoslynTest1
{
    using Multiplicity;

    /// <summary>
    /// http://social.msdn.microsoft.com/forums/en-us/roslyn 
    /// </summary>
    class Program
    {
        private readonly List<KeyValuePair<SyntaxNode, AttributeSyntax>> m_MultiplictyDefinitions = new List<KeyValuePair<SyntaxNode, AttributeSyntax>>();


        static void Main(string[] args)
        {
            var currentAsmPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            var path = Path.Combine(currentAsmPath, @"..\..\..\TestMulti\TestMulti.csproj");

            var project = Solution.LoadStandAloneProject(path);

            var doc = GetDocument(project);

            var sem = doc.GetSemanticModel();
            SyntaxTree tree = (SyntaxTree)doc.GetSyntaxTree();

            //check initial code
            string errors = String.Empty;
            errors = CheckForCompileErrors(tree);
            if (!String.IsNullOrEmpty(errors))
                throw new ArgumentException("The code to process already contains syntax errors: " + errors);

            //rewrite code
            var rewriter = new MultiplicityRewriter(project.Solution, sem, tree);
            var newRoot = rewriter.Visit(tree.GetRoot() as SyntaxNode);
            string newCode = newRoot.ToString();

            //check adjsuted code
            errors = CheckForCompileErrors(tree);
            if (String.IsNullOrEmpty(errors))
                Console.WriteLine(newCode);
            else
                Console.WriteLine(errors);



            Console.ReadLine();
        }

        public static string CheckForCompileErrors(SyntaxTree pTree)
        {
            Diagnostic[] dg = pTree.GetDiagnostics().ToArray();
            if (dg.Length > 0)
            {
                var error = new StringBuilder("The following compile error occured:" + Environment.NewLine);

                foreach (Diagnostic d in dg)
                    error.AppendLine("Info: " + d.ToString());
                return error.ToString();
            }
            else
            {
                return String.Empty;
            }
        }


        public void LoadSolution(string pSolutionFile)
        {
            var workspace = Roslyn.Services.Workspace.LoadSolution(pSolutionFile);
            var solution = workspace.CurrentSolution;
            var currentFileSemanticModel = (from p in solution.Projects
                                            from d in p.Documents
                                            where d.FilePath.Contains("currentFilePath")
                                            select d.GetSemanticModel()).SingleOrDefault();

            if (currentFileSemanticModel != null)
            {
                int position = File.ReadAllText(Path.Combine("basePath", "currentFilePath")).IndexOf("textToSearch");
                var foundSyntaxNode = currentFileSemanticModel.SyntaxTree.GetRoot().FindToken(position).Parent;
                var symbolInfo = currentFileSemanticModel.GetSymbolInfo(foundSyntaxNode);
                var result = symbolInfo.Symbol.FindReferences(solution);
                foreach (var reference in result)
                {
                    Console.WriteLine(reference.Definition.ToString());
                }
            }
        }



        private static IDocument GetDocument(IProject pProject)
        {
            foreach (var doc in pProject.Documents)
            {
                if (doc.FilePath.Contains("CodeToTest.cs"))
                    return doc;
            }
            return null;
        }

    }


    //    static void Main(string[] args)
    //    {
    //        var currentAsmPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    //        var path = Path.Combine(currentAsmPath, @"..\..\..\TestMulti\TestMulti.csproj");

    //        var project = Solution.LoadStandAloneProject(path);
    //        var solution = project.Solution;

    //        var document = GetDocument(project);

    //        //check initial code
    //        string errors = String.Empty;
    //        errors = CheckForCompileErrors((SyntaxTree)document.GetSyntaxTree());
    //        if (!String.IsNullOrEmpty(errors))
    //            throw new ArgumentException("The code to process already contains syntax errors: " + errors);


    //        var walker = new Walker(document.GetSemanticModel());
    //        walker.Visit((SyntaxNode)document.GetSyntaxRoot());


    //        foreach (var symbol in walker.Symbols)
    //        {
    //            var locationsByDocument =
    //            from referencedSymbol in symbol.FindReferences(solution)
    //            from referencedLocation in referencedSymbol.Locations
    //            group referencedLocation by referencedLocation.Document into g
    //            select g;

    //            foreach (var locationsInDocument in locationsByDocument)
    //            {
    //                var rewriter = new Rewriter(locationsInDocument);
    //                document = document.UpdateSyntaxRoot(rewriter.Visit((SyntaxNode)document.GetSyntaxRoot()));
    //            }
    //        }

    //        //check adjsuted code
    //        errors = CheckForCompileErrors((SyntaxTree)document.GetSyntaxTree());
    //        if (String.IsNullOrEmpty(errors))
    //        {
    //            string newCode = document.GetSyntaxRoot().ToString();
    //            Console.WriteLine(newCode);
    //        }
    //        else
    //            Console.WriteLine(errors);



    //        Console.ReadLine();
    //    }
    //}


    //class Rewriter : SyntaxRewriter
    //{
    //    private readonly IEnumerable<ReferenceLocation> references;
    //    public Rewriter(IEnumerable<ReferenceLocation> references)
    //    {
    //        this.references = references;          
    //    }

    //    public override SyntaxNode VisitElementAccessExpression(ElementAccessExpressionSyntax node)
    //    {
    //        return base.VisitElementAccessExpression(node);
    //    }

    //    public override SyntaxNode VisitMemberAccessExpression(MemberAccessExpressionSyntax node)
    //    {
    //        var rewritten = (MemberAccessExpressionSyntax)base.VisitMemberAccessExpression(node);

    //        if (IsReference(node))
    //        {
    //            rewritten = rewritten.WithName((SimpleNameSyntax)Syntax.ParseName("NewMemberName"));
    //        }

    //        return rewritten;
    //    }

    //    private bool IsReference(MemberAccessExpressionSyntax node)
    //    {
    //        // Check if node is in references.
    //        return true;
    //    }
    //}

    //class Walker : SyntaxWalker
    //{
    //    public IEnumerable<ISymbol> Symbols { get; private set; }

    //    private readonly ISemanticModel semanticModel;

    //    public Walker(ISemanticModel semanticModel)
    //    {
    //        this.semanticModel = semanticModel;
    //    }        


    //    public override void VisitFieldDeclaration(FieldDeclarationSyntax node)
    //    {
    //        var multiplicityAtt = node.AttributeLists.GetMultiplicityAttributes();
    //        if (multiplicityAtt != null)
    //        {
    //            this.Symbols = GetSymbols(node.Declaration);
    //        }
    //    }

    //    //http://connect.microsoft.com/VisualStudio/feedback/details/759204/getdeclaredsymbol-no-longer-works-on-fielddeclarationsyntax-in-v1-1
    //    private IEnumerable<ISymbol> GetSymbols(VariableDeclarationSyntax pVarDeclSyn)
    //    {
    //        return pVarDeclSyn.Variables.Select(v => semanticModel.GetDeclaredSymbol(v));
    //    } 
    //}



    public class MultiplicityRewriter : SyntaxRewriter
    {


        public CommonSyntaxTree Tree
        { get; set; }

        public ISemanticModel SemModel { get; set; }

        public ISolution Solut { get; set; }

        public MultiplicityRewriter(ISolution pSol, ISemanticModel pSem, CommonSyntaxTree pTree)
        {
            Tree = pTree;
            SemModel = pSem;
            Solut = pSol;
        }

        public override SyntaxNode VisitFieldDeclaration(FieldDeclarationSyntax node)
        {
            FieldDeclarationSyntax syntaxNode = base.VisitFieldDeclaration(node) as FieldDeclarationSyntax;
            if (syntaxNode == null) return null;

            var multiplicityAtt = node.AttributeLists.GetMultiplicityAttributes();
            if (multiplicityAtt == null)
                return syntaxNode;

            var varDecl = node.Declaration;

            var references = varDecl.FindAllReferences(this.SemModel, this.Solut, this.Tree);

            var type = multiplicityAtt.GetMultiplicityType();
            var kind = multiplicityAtt.GetMultiplicityKind();

            var newnode = node.ChangeToMultiplicityType(kind, type);
            var returnNode = newnode.RemoveMultiplicityAttribute(newnode.AttributeLists, true);
            return returnNode;
        }

        public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax node)
        {
            PropertyDeclarationSyntax syntaxNode = base.VisitPropertyDeclaration(node) as PropertyDeclarationSyntax;
            if (syntaxNode == null) return null;

            var multiplicityAtt = node.AttributeLists.GetMultiplicityAttributes();
            if (multiplicityAtt == null)
                return syntaxNode;

            var type = multiplicityAtt.GetMultiplicityType();
            var kind = multiplicityAtt.GetMultiplicityKind();


            var references = node.FindAllReferences(this.SemModel, this.Solut, this.Tree);

            var newnode = node.ChangeToMultiType(type);
            var returnNode = newnode.RemoveMultiplicityAttribute(newnode.AttributeLists, true);
            return returnNode;
        }














        //public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax node)
        //{
        //    SyntaxNode syntaxNode = base.VisitPropertyDeclaration(node);
        //    return syntaxNode;
        //    //TODO: ...

        //    if (!HasMultiplicityAttribute(node))//any other attrbiute
        //    {
        //        return syntaxNode;
        //    }

        //    //see if MultiplicityType is "any" and replace the type of the property by the the container type
        //    //transition rule (R-MDECL)     

        //    SyntaxNode nextNode = GetNextNode(syntaxNode);
        //    if (nextNode == null)
        //        return syntaxNode;

        //    var references = FindAllReferences(syntaxNode);

        //    return syntaxNode;
        //}

        //public override SyntaxNode VisitAttribute(AttributeSyntax node)
        //{
        //    SyntaxNode syntaxNode =  base.VisitAttribute(node);

        //    if (!((IdentifierNameSyntax)node.Name).Identifier.ValueText.Equals("Multiplicity"))
        //    {
        //        return syntaxNode;
        //    }


        //    //syntaxNode.Parent = AttributeList --> get the parent of the list
        //    var multiNode = syntaxNode.Parent.Parent;
        //    //var refs = FindAllReferences(multiNode);
        //    return syntaxNode.Parent.RemoveNode(syntaxNode, SyntaxRemoveOptions.KeepExteriorTrivia);

        //}

        //private static bool HasMultiplicityAttribute(MemberDeclarationSyntax node)        
        //{
        //    return node.AttributeLists.SelectMany(a => a.ChildNodes().OfType<AttributeSyntax>())
        //                                         .Any(a => ((IdentifierNameSyntax)(a.Name)).Identifier.ValueText.Equals("Multiplicity"));
        //}





    }



}
